/*******************************************************************************
 *     ___                  _   ____  ____
 *    / _ \ _   _  ___  ___| |_|  _ \| __ )
 *   | | | | | | |/ _ \/ __| __| | | |  _ \
 *   | |_| | |_| |  __/\__ \ |_| |_| | |_) |
 *    \__\_\\__,_|\___||___/\__|____/|____/
 *
 *  Copyright (c) 2014-2019 Appsicle
 *  Copyright (c) 2019-2024 QuestDB
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/

package io.questdb.test.cutlass.http;

import io.questdb.std.CharSequenceObjHashMap;
import io.questdb.std.Rnd;
import io.questdb.std.str.Utf8Sequence;
import io.questdb.std.str.Utf8StringSink;
import io.questdb.test.AbstractCairoTest;
import io.questdb.test.tools.TestUtils;
import org.junit.Test;

public class JsonExecuteApiFuzzTest extends AbstractCairoTest {

    @Test
    public void testFullFuzz() throws Exception {
        Rnd rnd = TestUtils.generateRandom(LOG);

        var emptyColParams = new CharSequenceObjHashMap<String>();
        emptyColParams.put("cols", ",");

        var limitParams = new CharSequenceObjHashMap<String>();
        limitParams.put("limit", "1");

        var badLimitParams = new CharSequenceObjHashMap<String>();
        badLimitParams.put("limit", "not a number");

        var nonExistingColParams = new CharSequenceObjHashMap<String>();
        nonExistingColParams.put("cols", "z");

        var badUtf8Params = new CharSequenceObjHashMap<Utf8Sequence>();
        Utf8StringSink badUtf8Sink = new Utf8StringSink();
        badUtf8Sink.put("select").putAny((byte) 0xC3).putAny((byte) 0x28);
        badUtf8Params.put("cols", badUtf8Sink);

        getSimpleTester()
                .withForceRecvFragmentationChunkSize(Math.max(1, rnd.nextInt(1024)))
                .withForceSendFragmentationChunkSize(Math.max(1, rnd.nextInt(1024)))
                .withSendBufferSize(Math.max(1024, rnd.nextInt(4099)))
                .run((HttpQueryTestBuilder.HttpClientCode) (engine, sqlExecutionContext) -> {
                            engine.execute("create table xyz as (select rnd_int() a, rnd_double() b, timestamp_sequence(0,1000) ts from long_sequence(1000)) timestamp(ts) partition by hour");
                            engine.execute("create table growing as (select rnd_int() a, rnd_double() b, timestamp_sequence(0,1000) ts from long_sequence(1000)) timestamp(ts) partition by hour");
                            engine.execute("create table arr as (select rnd_double_array(1, 100, 10), rnd_varchar() from long_sequence(100))");
                            engine.execute("create table altered as (select rnd_int() a, rnd_double() b, timestamp_sequence(0,1000) ts from long_sequence(5)) timestamp(ts) partition by hour");

                            var requestResponse = new Object[][]{
                                    {"select count() from xyz", "{\"query\":\"select count() from xyz\",\"columns\":[{\"name\":\"count\",\"type\":\"LONG\"}],\"timestamp\":-1,\"dataset\":[[1000]],\"count\":1}"},
                                    {"select a from xyz limit 1", "{\"query\":\"select a from xyz limit 1\",\"columns\":[{\"name\":\"a\",\"type\":\"INT\"}],\"timestamp\":-1,\"dataset\":[[-1148479920]],\"count\":1}"},
                                    {"select a from xyz limit 1", "{\"query\":\"select a from xyz limit 1\",\"error\":\"empty column in query parameter\"}", emptyColParams},
                                    {"select a from xyz", "{\"query\":\"select a from xyz\",\"columns\":[{\"name\":\"a\",\"type\":\"INT\"}],\"timestamp\":-1,\"dataset\":[[-1148479920]],\"count\":1}", limitParams},
                                    // limit params are forgiving - invalid limit is ignored, by design
                                    {"select a from xyz limit 1", "{\"query\":\"select a from xyz limit 1\",\"columns\":[{\"name\":\"a\",\"type\":\"INT\"}],\"timestamp\":-1,\"dataset\":[[-1148479920]],\"count\":1}", badLimitParams},
                                    {"select a from xyz limit 1", "{\"query\":\"select a from xyz limit 1\",\"error\":\"column not found: 'z'\"}", nonExistingColParams},
                                    {"select b from xyz limit 5", "{\"query\":\"select b from xyz limit 5\",\"columns\":[{\"name\":\"b\",\"type\":\"DOUBLE\"}],\"timestamp\":-1,\"dataset\":[[0.8043224099968393],[0.08486964232560668],[0.0843832076262595],[0.6508594025855301],[0.7905675319675964]],\"count\":5}"},
                                    {"select ts, b from xyz limit 15", "{\"query\":\"select ts, b from xyz limit 15\",\"columns\":[{\"name\":\"ts\",\"type\":\"TIMESTAMP\"},{\"name\":\"b\",\"type\":\"DOUBLE\"}],\"timestamp\":0,\"dataset\":[[\"1970-01-01T00:00:00.000000Z\",0.8043224099968393],[\"1970-01-01T00:00:00.001000Z\",0.08486964232560668],[\"1970-01-01T00:00:00.002000Z\",0.0843832076262595],[\"1970-01-01T00:00:00.003000Z\",0.6508594025855301],[\"1970-01-01T00:00:00.004000Z\",0.7905675319675964],[\"1970-01-01T00:00:00.005000Z\",0.22452340856088226],[\"1970-01-01T00:00:00.006000Z\",0.3491070363730514],[\"1970-01-01T00:00:00.007000Z\",0.7611029514995744],[\"1970-01-01T00:00:00.008000Z\",0.4217768841969397],[\"1970-01-01T00:00:00.009000Z\",0.0367581207471136],[\"1970-01-01T00:00:00.010000Z\",0.6276954028373309],[\"1970-01-01T00:00:00.011000Z\",0.6778564558839208],[\"1970-01-01T00:00:00.012000Z\",0.8756771741121929],[\"1970-01-01T00:00:00.013000Z\",0.8799634725391621],[\"1970-01-01T00:00:00.014000Z\",0.5249321062686694]],\"count\":15}"},
                                    {"select a, z from xyz", "{\"query\":\"select a, z from xyz\",\"error\":\"Invalid column: z\",\"position\":10}"},
                                    {"create table if not exists abc(x int)", "{\"ddl\":\"OK\"}"},
                                    {"select \"µ\" from xyz", "{\"query\":\"select \\\"µ\\\" from xyz\",\"error\":\"Invalid column: µ\",\"position\":7}"},
                                    {new Utf8StringSink().put("select").putAny((byte) 0xC3).putAny((byte) 0x28), "{\"query\":\"selectￃ(\",\"error\":\"Bad UTF8 encoding in query text\",\"position\":0}"},
                                    {new Utf8StringSink().put("xyz"), "{\"query\":\"xyz\",\"error\":\"utf8 error in column list\"}", badUtf8Params},
                                    // empty query
                                    {"", "{\"error\":\"empty query\",\"query\":\"\",\"position\":\"0\"}"},
                                    {"backup table xyz", "{\"query\":\"backup table xyz\",\"error\":\"backup is disabled, server.conf property 'cairo.sql.backup.root' is not set\",\"position\":0}"},
                                    {"insert into growing values (0, 0, '2000')", "{\"dml\":\"OK\"}"},
                                    // non-empty query text with an effectively empty query
                                    {"--", "{\"error\":\"empty query\",\"query\":\"--\",\"position\":\"0\"}"},
                                    {"update growing set a = 42 where a != a", "{\"dml\":\"OK\",\"updated\":0}"},
                                    // long-ish array result
                                    {"select * from arr", "{\"query\":\"select * from arr\",\"columns\":[{\"name\":\"rnd_double_array\",\"type\":\"ARRAY\",\"dim\":1,\"elemType\":\"DOUBLE\"},{\"name\":\"rnd_varchar\",\"type\":\"VARCHAR\"}],\"timestamp\":-1,\"dataset\":[[[0.7890019369191074,0.4610606296625652],\"\\\"J_;\\\\ZN`4n\"],[[0.579774945269956,0.05934627081165145,0.21751667676871733,0.6921002448786602,0.4736992197947769,0.28813673096485004,0.5149820798192002,0.5574881757959644,0.09173289924860306],\"I8nuwzy%w\"],[[0.9268888466899076,0.32527880412219246,0.7143343582068687],\"+r?Zi<c\"],[[0.03651717640331276,0.4780397097988206,0.7800238438224233],\"mnm2t\"],[[0.7150611945026853,0.008311264106289795],\"d۶ΗቭG\uD9FE\uDF28RfAɥ\"],[[0.37938407400399465,0.6641072224015451],\"&81\"],[[0.19442309882261055,0.5284816356269514,0.10293550703877974,0.7650675662285991,0.12908118532110446,0.740411035376218,0.44939138870453255,0.17924407148923427],\"N1?:F;a]#\"],[[0.6530508254565319,0.3854989856103621,0.24447687794057837,0.41212883675736234,0.4428796741037522,0.9032451307371845],\"k!qf=HG\"],[[0.28395059410588797,0.7030642628778971],\"캜u\uDBBA\uDDFFTɓ\"],[[0.9470648776446439,0.2187107725996701,0.728656099138432,0.503884052632148,0.5844403167063175],\"Y7\\\\\"],[[0.4311025113909387,0.6112596771591841],\"LSf:\"],[[0.6107450771316009,0.9395205626178185,0.9408452330399676],\"4@h>boiRh\"],[[0.2589212630564731,0.3903546704966725,0.32558060779195785,0.45610388007908687,0.26771349688385504,0.8177700009080593,0.467770611444258,0.034414363713293206,0.11866818825008785],\"⦐g0+\uDB81\uDFCEmͳ\uDA1F\uDDF8㥜⛌\"],[[0.6617008816211023,0.2102621612916762],\"ԱΑҺ\uDAA3\uDE3F\uDA3A\uDC50CӲ܈bƅ\"],[[0.40669574015787113,0.45177070338077596,0.21123430990311232],\"Bڭ7⮞q\"],[[0.5451326034530918,0.601441303700847,0.7575375268875545,0.6281884936083273],\">߸3W(ԩT\"],[[0.5310245477725253,0.9906754009871559],\"選肮~ʚǚo\"],[[0.4609235669452154,0.515123379740694,0.9884235203800106],\"뻶z^씁\u197A!\uDB62\uDCE1?\"],[[0.38608539278112075,0.7406246567933614,0.29144895613033484,0.613817048673222],\"cN\uDB42\uDC31\uDB3B\uDC03\"],[[0.8248135128226413,0.1881568411270398],\"hvKfp\"],[[0.392898162325679,0.33351333945145833,0.4196782585930148,0.05378408978426652,0.458748704992164,0.5016630085845014,0.49656849356575317,0.6257497020843609,0.1633370519487536],\"cꓣ乘c8\uDB8A\uDDF9\uDB28\uDF57ą`\"],[[0.22640171627486083,0.17757068275306354,0.6097548751666031],\"\uD977\uDD99慈\uDBEC\uDE20\uDB20\uDD9Cc\"],[[0.38256253486033487,0.2401044755301821,0.10825958048357298,0.9449579978766822,0.11140669578722817,0.5920284769370782,0.3838576855408572],\"薰\uDAB3\uDCA0ѷ\uDAC8\uDCCC郇d\\\"\"],[[0.4024292809446638,0.810157476628622],\"Jqo84\\\\p=P\"],[[0.49461223909000585,0.691884206973425,0.17566102131682015],\"8ӱۑޅꪜ\"],[[0.7665035432130607,0.7660548927941956,0.9540706428110737],\"윦\u07B9uhN\uDA8C\uDC68Ō$\"],[[0.03159070506419415,0.5546600237228556],\"YW٦h\uD9C5\uDC36Ò\uD9E6\uDC27\u008B?\"],[[0.48893221795402586,0.5447511017349794,0.4863846872665012],\"\uDA83\uDE29BU\uDB94\uDCEDꋫ㻪\uDA49\uDD0F\"],[[0.7987042002552399,0.9010953692323533,0.043437005903916526,0.9411878730177388],\"b0w\"],[[0.7056434230427517,0.6227213435973901],\"H?-N\"],[[0.33602670308409655,0.5995673717089761,0.26338838118491803],\"ެԅ躸\uDAAD\uDEF8ٙ\u0086\"],[[0.8314470966820114,0.599883537654908,0.451081922221094],\"\uDAD6\uDCD9齆S\uDB96\uDF1AϊG\uF058\"],[[0.592490224404158,0.01810381914154824],\"射X\\\"\"],[[0.950085457538888,0.6716701814813273,0.14903579951135448,0.6622455749196166,0.6609252628910544],\"\uDB1B\uDE8A\uD935\uDFA0dg\uDADC\uDCA9p¤\"],[[0.854803640772796,0.09250720690837777,0.2105335245565415],\"R0|Bn\"],[[0.54558350465742,0.829438753220087],\"8cxx{N)@D\"],[[0.7435275606846287,0.8876882969284496,0.3095784301869926,0.18244628427066079,0.45132268261125164],\"Y`2d<\"],[[0.46863946717717186,0.9748023951436231,0.813944460183829,0.8352875710642096,0.25427334576441285],\"kF2;v|_,W~\"],[[0.4939947779599977,0.9498540955144517],\"BIoT\"],[[0.881038984518274,0.08569546758640434,0.630055559776544,0.541709839493781,0.82175882740253,0.19918205371579645],\"銾ɓwU8щ)\"],[[0.46554262679413927,0.6776313554871427],\"St\uE0D9\"],[[0.8742308332828906,0.13424351785994992,0.8247150521380682,0.17741195636381546,0.07928080837256268,0.6198680808634015,0.09802867311583952,0.8768317206347732,0.02683569462204316],\"=F䆦\"],[[0.8625961698646317,0.900330188877589,0.11494429495724134,0.08757485992717795,0.5694205411840019,0.8282723150989655,0.7201409952630464],\"ε^\u07FC읝わ\uDB60\uDFA9Ư\"],[[0.7575673838304599,0.27876725982813666,0.1738856084590915],\"FiQk&bQ*\"],[[0.8449852333665553,0.2936114135622353],\"ݛW`\uDA0A\uDE9Fq.R8\"],[[0.4551016924621597,0.6705944127717545,0.09589847780707561,0.9072682731243069],\"m﹗鮮A\uE1C7&D촹\"],[[0.5919041900366419,0.6045117767406492,0.33554372193969384,0.6407050524553451,0.8584894529441149,0.6976099915016283,0.29587485203705965,0.9207792697427253,0.5323752673615637,0.1419359482096504],\"tȿ&=<ꎒ\"],[[0.6850204526433905,0.37302709618400487,0.49876229705934116],\"ߘӑⓘ5驱\uD933\uDD8Eꗎ\uDAAE\uDC94/N\"],[[0.15683461898806716,0.19543033174255064],\"@s0 8\"],[[0.24045223865873133,0.5724942556008196,0.2565975916889538,0.8817450428712124,0.09289111506620318,0.09170236636115081,0.7649302369379225],\"g!b&#0<pY_\"],[[0.839969315770081,0.39924764680123614],\",(%s;S%mX1\"],[[0.910288811401709,0.6382724551099219,0.3186532680955032,0.4534952721554767,0.5903590410896078],\"9pc/\"],[[0.39769182420989735,0.05384050475509661,0.9928279232261719,0.9780114123682491,0.636066719559969,0.5938537940557289,0.8414449890107711,0.3450177877005235,0.06968527476986786],\",@1oq\"],[[0.9576253579882467,0.8693253469830173],\"!#<#`wWz#O\"],[[0.8096023825900682,0.20447752781256245,0.06545296724783167,0.16193359939879248,0.6920789953062617,0.9839578584426564,0.6146103867116717,0.9227470238568934],\"])#\"],[[0.8963083008136447,0.2113350376901868],\"}ElFOI:WH>\"],[[0.03153901850818175,0.6629166974319927,0.7294934317537942,0.10591194260080428,0.6965491902896557],\"XX1]_CfNgO\"],[[0.03630597261924018,0.9675073076454067,0.09028840338365252],\"R[A\"],[[0.8305116665113652,0.5946865482923515,0.9080865693673253,0.20969266908815853,0.4688682086896119,0.24494279708414046,0.49670129521948725,0.3404397168566058,0.876042283158669],\"]\\\"feF?u:\"],[[0.31746609189345154,0.6989734144831369,0.3417414079163926,0.13208446158879616,0.6886366354986597,0.021198310817298283],\"<-VRRO\"],[[0.7602534142717986,0.9323884083994887,0.6593368223071437,0.7818373619920722,0.4020581372033235,0.10474690642862328,0.005578426983181339,0.9157774102416812,0.35783650991836324,0.5604080305623532],\"u=3\\\\c\"],[[0.7437607932294104,0.9849076602993236],\"ν\uDAD1\uDD87;]\"],[[0.2322305864548041,0.09514666015909612,0.5131051284130206,0.8570537660428786],\"TG\uDAAA\uDF0Eʆ4G\uDB37\uDF20\"],[[0.8335236804317315,0.9122986343641115,0.2602206500051124,0.09823995334846469,0.5992751863441315,0.8930439553624496,0.1275999688540307,0.3843908202580162,0.261316053829179,0.703794925337531],\"\uD8ED\uDF31퍍㚔Ҫˑօ&Sl\"],[[0.731665210338385,0.16229032278999633,0.042090144889132985],\"w(rlxsm0\"],[[0.8229816943636918,0.06827595511448381],\"\uDB7A\uDC9D냵ǅ˭\u07BE\uEBCD\"],[[0.03844293913646801,0.651700754415731,0.6601800008841533,0.5105898152916307,0.5502284711796974,0.8964157300408011,0.8728278961186127,0.7613752826698491],\"iM|~X$1\"],[[0.6003961461618768,0.6602726890969307,0.4393180020980574,0.5959196063221096,0.3638573436022059],\"؝\uD90C\uDF323w0곔\u05EC⛼\"],[[0.41009046542372085,0.8946044523367012],\"3PPI꓾Ȱ濁\uDB8A\uDDA4\uD93E\uDFC8\uD9B1\uDED1\"],[[0.16090051443865017,0.3999559532789345],\"z矔ExoɃ\"],[[0.8297350468804592,0.3383740253679671],\"ᆄk鍔\uDA3C\uDDB7J\uDAF3\uDEB8R\"],[[0.2027715864356816,0.494888113404614,0.4354886838673644,0.2970117665846371,0.4945340073628469,0.525107471012383,0.8405102603802288],\"5q?s\"],[[0.26401393393827766,0.04414051404204966,0.6088285896863561,0.8703515343469586],\"˰Xꡉ7K+赐皡\"],[[0.29243418599620896,0.027478206434256025,0.9176960180410162,0.11515431304526058,0.06623410553105447,0.3084904759724103,0.5914184295827997,0.40527428406938615,0.492092810603346],\"HW.\"],[[0.15746329673959203,0.8162520991305962,0.9939153204644653],\"\uDA94\uDD66\uD9F9\uDDFF랮\uE51F\uFEFEF\uD94E\uDE55홒~\uD8D9\uDDAB\"],[[0.047256821939134164,0.1899585982342693,0.1866755899221303,0.3307144282695307],\"K1,@Sp7\"],[[0.7912356264848054,0.12064941326597967,0.3026164978548048,0.2866428651933869,0.8345579618929828,0.6611150825594797,0.04003207773973072,0.5380682508407864],\"pn</>A\"],[[0.5941395946914931,0.087266150680685,0.5674355397756983],\"K\\\\)^\"],[[0.8060870636078219,0.9400159704076865,0.8668146968076318],\"\u05F5䐜딦듶\\\\܁\"],[[0.08445456222226766,0.5843271489365648,0.08702465548163629,0.8899821896857749,0.6190809301115854,0.9453237073413382],\"p*-CPX1s\"],[[0.28791752610529364,0.9310812532772312],\"~=wk}=bd3W\"],[[0.8313871246088479,0.7284058427535202,0.1414365550701272,0.8061321619804117,0.8815538906377007,0.15169665193026627,0.25126679321384404,0.7135107290410793,0.29709701671017563],\" J{\"],[[0.20179716320100094,0.730383749200444,0.5084590105244766,0.6880936293673171,0.4375016303261601,0.9043154065764325],\"0+W$`\"],[[0.17817118956694633,0.010207560952646522,0.823215832503392,0.313376794732785],\"\uD9FA\uDD9F\uF202\uDADB\uDEE4͗\"],[[0.49650763072693394,0.7773954776688469],\"ᴲބ7.\\\\Ξx왲<\"],[[0.7667826175390616,0.4073604354518575,0.034950176825901624,0.05833312306026661,0.31997186333005556,0.9061864590254354,0.09385416211614217,0.3769861900644069,0.8777093148625262,0.6009736846298588],\"h$b밗ʣe\"],[[0.5634687338603241,0.5986668020879318,0.274286831992817,0.40367576311289854,0.6495300966710479,0.7654210730388539,0.7200408901022505,0.6236380739922823,0.6588206043025011],\"EP;7\"],[[0.13559941387757002,0.5988160576704303,0.4835310533687889,0.18033766510984428,0.14502990718759012,0.7546378553128547],\"ԕ煞\uDBE5\uDFEE\"],[[0.9329239623700321,0.8827763660567141,0.6652179190003759,0.3383751106787871],\"{}3Q0t\"],[[0.6395853519633263,0.2100773710498347,0.9879971680320252],\"ELf\"],[[0.024331580098810313,0.16240203603976444,0.44082993190948894],\"i\\\\@] \"],[[0.30855506185901704,0.6938746675972347],\"}=1\"],[[0.5540101142075512,0.04304600529671643,0.13573735961116717,0.08181516200419314],\"^FHgm\"],[[0.566169810958062,0.9501305749643243],\"<Qo\\\\jamh?)\"],[[0.9308356667335997,0.6870681054054534,0.501940278279924,0.07645662484110993,0.2728902960470929],\"|4OD:jQ<\"],[[0.4667007104042522,0.011935837371142788,0.3372682591751851],\"ژZܖ찷<ʕ\"],[[0.8046171305348603,0.7240291983045418],\"=pqp/\"],[[0.7190356978367048,0.5036823870960982,0.006540826567466351,0.5228631165132572,0.8769118345681355,0.8077692766557464,0.3218524406103459],\"I:dR\"],[[0.4880108812214099,0.7774185439635324],\"ӡӾT\"],[[0.2129007587819436,0.16827603195378416,0.6252245992436137],\"bz7E+pf\"]],\"count\":100}"},
                                    {"alter table altered add column if not exists c int", "{\"ddl\":\"OK\"}"},
                                    {"explain select * from arr", "{\"query\":\"explain select * from arr\",\"columns\":[{\"name\":\"QUERY PLAN\",\"type\":\"STRING\"}],\"timestamp\":-1,\"dataset\":[[\"PageFrame\"],[\"&nbsp;&nbsp;&nbsp;&nbsp;Row forward scan\"],[\"&nbsp;&nbsp;&nbsp;&nbsp;Frame forward scan on: arr\"]],\"count\":3}"}
                            };

                            var candidateCount = requestResponse.length;
                            try (TestHttpClient testHttpClient = new TestHttpClient()) {
                                testHttpClient.setKeepConnection(true);
                                int iterCount = rnd.nextInt(100);
                                for (int i = 0; i < iterCount; i++) {
                                    int index = rnd.nextInt(candidateCount);
                                    Object[] testCase = requestResponse[index];


                                    if (testCase[0] instanceof Utf8Sequence utf8Sql) {
                                        CharSequenceObjHashMap<Utf8Sequence> queryParams = null;
                                        if (testCase.length > 2) {
                                            queryParams = (CharSequenceObjHashMap<Utf8Sequence>) testCase[2];
                                        }

                                        testHttpClient.assertGet(
                                                "/api/v1/sql/execute",
                                                testCase[1].toString(),
                                                utf8Sql,
                                                queryParams
                                        );
                                    } else {
                                        CharSequenceObjHashMap<String> queryParams = null;
                                        if (testCase.length > 2) {
                                            queryParams = (CharSequenceObjHashMap<String>) testCase[2];
                                        }

                                        testHttpClient.assertGet(
                                                "/api/v1/sql/execute",
                                                testCase[1].toString(),
                                                testCase[0].toString(),
                                                queryParams
                                        );
                                    }
                                }
                            }
                        }
                );
    }
}
